package com.opennews.openplatform

import grails.compiler.GrailsCompileStatic
import grails.gorm.transactions.Transactional
import groovy.transform.CompileDynamic
import org.hibernate.criterion.CriteriaSpecification
import org.hibernate.sql.JoinType

@Transactional
@GrailsCompileStatic
class JoinAccountGroupRequestService {
    UserService userService

    /**
     * Counts received requests by receiver user id.
     * @param receiverUserId: String of receiver user id.
     * @return: Count of requests.
     */
    int countReceived(String receiverUserId) {
        return JoinAccountGroupRequest.countByReceiver(User.proxy(receiverUserId))
    }

    /**
     * Queries pending requests for receiver.
     * @param receiverUsername
     * @param pageSize
     * @param pageIndex
     * @return
     */
    @Transactional(readOnly = true)
    @CompileDynamic
    List<Map<String, ?>> queryReceiving(String receiverUserId, Integer pageSize, Integer pageIndex) {
        // Queries receiver's the request list.
        return JoinAccountGroupRequest.createCriteria().list {
            // Transforms the result data format to Map instance.
            resultTransformer(CriteriaSpecification.ALIAS_TO_ENTITY_MAP)

            // Sets the join type for owner property in order to get its properties.
            createAlias("owner", "owner", JoinType.INNER_JOIN)

            // Sets the join type for sender property in order to get its properties.
            createAlias("sender", "sender", JoinType.INNER_JOIN)

            // Sets the query condition.
            eq("receiver.id", receiverUserId)

            // Specifies the property we need.
            projections {
                property("id", "id")
                property("owner.username", "ownerUsername")
                property("owner.fullName", "ownerFullName")
                property("owner.avatarFilename", "ownerAvatarFilename")
                property("sender.username", "senderUsername")
                property("sender.fullName", "senderFullName")
                property("lastUpdated", "lastUpdated")
            }

            // How many rows are return for this trip.
            maxResults(pageSize)

            // Where to take the rows.
            firstResult(pageSize * pageIndex)

            order("lastUpdated", "desc")
        }.collect { Map<String, ?> p -> [
            id: p.id,
            owner: [
                username: p.ownerUsername.toString(),
                fullName: p.ownerFullName,
                avatarUrl: userService.getAvatarUrl(p.ownerAvatarFilename.toString()),
            ],
            sender: [
                username: p.senderUsername.toString(),
                fullName: p.senderFullName
            ],
            lastUpdated: p.lastUpdated
        ]}
    }

    /**
     * Adds new request or updates existing one.
     * @param accountGroupId
     * @param senderId
     * @param receiverId
     * @param roles
     */
    @CompileDynamic
    void addOrUpdate(String accountGroupId, String senderId, String receiverId, List<String> roles) {
        def accountGroup = AccountGroup.proxy(accountGroupId)
        def sender = User.proxy(senderId)
        def receiver = User.proxy(receiverId)
        def requestCount = JoinAccountGroupRequest.countByAccountGroupAndSenderAndReceiver(accountGroup, sender, receiver)

        // roles.join(",") returns result like ""4028813986a6c2610186a6c28a640042","4028813986a6c2610186a6c28a610041","4028813986a6c2610186a6c28a680043"".
        // But "4028813986a6c2610186a6c28a640042,4028813986a6c2610186a6c28a610041,4028813986a6c2610186a6c28a680043" is expected.
        // That's why calls collect before join.
        def joinedRoles = roles.collect{ it }.join(",")

        if (requestCount == 0) {
            def ownerId = AccountGroupUser.where {
                accountGroup == accountGroup &&
                    isAccountGroupCreator == true
            }.property("user.id").get() as String

            new JoinAccountGroupRequest().with {
                it.accountGroup = accountGroup
                it.owner = User.proxy(ownerId)
                it.sender = sender
                it.receiver = receiver
                it.roles = joinedRoles
                it.save()
            }
        } else {
            // Updates roles just in case it has been changed.
            JoinAccountGroupRequest.where {
                accountGroup == accountGroup &&
                    sender == sender &&
                    receiver == receiver
            }.updateAll([
                roles: joinedRoles
            ])
        }
    }

    /**
     * Request receiver accepts to this request.
     * @param id: The string of request id.
     * @param receiverId: The string of the user as receiver.
     */
    void accept(String id, String receiverId) {
        def user = User.proxy(receiverId)
        def data = JoinAccountGroupRequest.findByIdAndReceiver(id, user)

        if (data != null) {
            def roleIds = data.roles.split(",")

            // Saves relationship between user and account group.
            new AccountGroupUser().with {
                it.accountGroup = data.accountGroup
                it.user = data.receiver
                it.isAccountGroupCreator = false
                it.save()
            }

            // Saves all user roles.
            roleIds.each { String roleId ->
                new UserRole().with {
                    it.accountGroup = data.accountGroup
                    it.user = user
                    it.role = Role.proxy(roleId)
                    it.save()
                }
            }

            // Deletes the completed request.
            deleteReceiving(id, receiverId)
        }
    }

    /**
     * Request receiver rejects to this request.
     * @param id: The string of request id.
     * @param receiverId: The string of the user as receiver.
     */
    void reject(String id, String receiverId) {
        // Deletes the completed request.
        deleteReceiving(id, receiverId)
    }

    /**
     * Deletes request based on its id and receiver id.
     * @param id: The string of request id.
     */
    @CompileDynamic
    private void deleteReceiving(String id, String receiverId) {
        JoinAccountGroupRequest.where {
            id == id &&
                receiver == User.proxy(receiverId)
        }.deleteAll()
    }
}